Interacting with your Kitties
Add pallet capabilities that unleash the potential of your Substrate Kitty application.
#
OverviewUp until this point in the tutorial, we've built a chain capable of only creating and tracking the ownership of Kitties. In this part of the tutorial, we want to make our runtime more like a game by introducing other functions like buying and selling Kitties. In order to achieve this, we'll first need to enable users to update the price of their Kitty. Then we can add functionality to enable users to transfer, buy and breed Kitties.
#
Learning outcomesโก๏ธ Learn how to create a dispatchable that updates an object in storage.
โก๏ธ Getting a value from a struct in storage.
โก๏ธ How to use the transfer
from FRAME's Currency trait.
โก๏ธ How to write sanity check using ensure!()
.
#
Steps#
1. Set a price for each KittyIn the helper file for this part of the tutorial, you'll notice that the structure of set_price
is already laid out.
Your job is to replace ACTION lines #1, #2 and #3 lines with what you'll learn in sections A-D below.
#
A. Checking Kitty ownerAs we create functions which modify objects in storage, we should always check that only the appropriate users are successful when calling those dispatchable functions.
The general pattern for an ownership check will look something like this:
Your turn! Paste in this code snippet to replace ACTION #1:
#
B. Updating the price of our Kitty objectEvery Kitty object has a price attribute that we've set to [None
] as a default value inside the
mint
function in Part II:
To update the price of a Kitty, we'll need to:
- Get the Kitty object in storage.
- Update the object with the new price.
- Push it back into storage.
Changing a value in an existing object in storage would be written in the following way:
note
Rust expects you to declare a variable as mutable (using the mut
keyword) whenever its value is going to be updated.
Your turn! Paste in the following snippet to replace the ACTION #2 line:
#
D. Deposit an eventOnce all checks are passed and the new price is written to storage, we can deposit an event just like we did in Part III. Replace the line marked as ACTION #3 with:
Now whenever the set_price
dispatchable is called successfully, it will emit a PriceSet
event. ๐
#
2. Transfer a KittyYou already have the tools and knowledge you'll need to create the transfer functionality from step 1. The main difference is that there are two parts to achieving this:
- A dispatchable function called
transfer()
: this is a publicly callable dispatchable exposed by your pallet. - A private function called
transfer_kitty_to()
: this will be a private helper function called bytransfer()
to handle all storage updates when transferring a Kitty.
Separating the logic this way makes the private transfer_kitty_to()
function reusable
by other dispatchable functions of our pallet, without needing to duplicate code. In our case, we're going to reuse it for
the buy_kitty
dispatchable we're creating in the next section.
transfer
#
Paste in the following snippet to replace ACTION #5 in the template code:
By now the above pattern should be familiar. We always check that the transaction is signed; then we verify that the Kitty
being transfer is owned by the sender of this transaction; and last we call the transfer_kitty_to
helper to update
all storage items appropriately.
transfer_kitty_to
#
Now, the transfer_kitty_to
function will be a helper to perform all storage updates once a Kitty has been bought and sold.
All it needs to do is perform safety checks and update the following storage items:
KittiesOwned
: to update the owner of the Kitty.Kitties
: to reset the price in the Kitty object to None.
Copy the following to replace ACTION #6:
Notice the use of [#transactional]
which we imported at the very beginning of this tutorial. It allows us to write dispatchable functions that will only write to storage at the same time as the helper functions it calls, making sure all storage writes happen together.
#
3. Buy a Kitty#
A. Check a Kitty is for SaleWe'll need to ensure 2 things before we can allow the user of this function to purchase a Kitty: first, check that the Kitty is for sale; and second, check whether the Kitty's current price is within the user's budget and whether the user has enough free balance.
Replace line ACTION #7:
In a similar vain, we have to verify whether the user has the capacity to receive a Kitty โ remember we're using
a BoundedVec
that can
only hold a fixed number of Kitties, defined in our pallet's MaxKittyOwned
constant.
One last check before we can allow this user to call this dispatchable (paste this in following the last snippet):
#
B. Making a PaymentIn Step 2, we added the functions necessary to transfer the ownership of our
Kitties. But we haven't yet touched on the currrency associated to our pallet.
In this step we'll learn how to use FRAME's Currency trait to adjust account balances
using its very own transfer
method. It's useful to understand why it's important to use the transfer
method in particular and how we'll be accessing it:
The reason we'll be using it is to ensure our runtime has the same understanding of currency throughout the pallets it interacts with. The way that we ensure this is to use the
Currency
trait fromframe_support
.Conveniently, it handles a
Balance
type, making it compatible withBalanceOf
type we created forkitty.price
. Take a look at how thetransfer
function we'll be using is structured (from the Rust docs):
Now we can make use of the Currency
type in our pallet's Config
trait and ExistenceRequirement
โ that we
initially started with in Part I.
Update the balances of both the caller of this function and the receiver, replacing ACTION #8:
#
4. Breed KittiesThe logic behind breeding two Kitties is to multiply each corresponding DNA segment from two Kitties, which will produce a new DNA sequence. Then, that DNA is used when minting a new Kitty. This helper function is already provided for you in the template file for this section.
Paste in the following to complete the breed_kitty
function, replacing line ACTION #10:
Now that we've used the user inputs of Kitty IDs and combined them to create a new unique Kitty ID, we can
use the mint()
function to write that new Kitty to storage. Replace line ACTION #11:
#
5. Genesis configurationThe final step before our pallet is ready to be used is to set the genesis state of our storage items. We'll make use of
FRAME's [pallet::genesis_config]
to do this. Essentially, we're declaring what the Kitties object in storage contains
in the genesis block. Copy the following code to replace ACTION #12:
To let our chain know about our pallet's genesis configuration, we need to modify the chain_spec.rs
file in our project's node
folder. Go to /node/src/chain_spec.rs
and add the following inside the testnet_genesis
function:
runtime/src/lib.rs
and interact with your Kitties#
6. Update If you've completed all of the preceding parts and steps of this tutorial, you're all geared up to run your chain and start interacting with all the new capabilities of your Kitties pallet.
Build and run your chain using the following commands:
Now check your work using the Polkadot-JS Apps UI just like we did in the previous part. Once your chain is running and connected to the PolkadotJS Apps UI, perform these manual checks:
- Fund multiple users with tokens so they can all participate
- Have each user create multiple Kitties
- Try to transfer a Kitty from one user to another using the right and wrong owner
- Try to set the price of a Kitty using the right and wrong owner
- Buy a Kitty using an owner and another user
- Use too little funds to purchase a Kitty
- Overspend on the cost of the Kitty and ensure that the balance is reduced appropriately
- Breed a Kitty and check that the new DNA is a mix of the old and new
After all of these actions, confirm that all users have the right number of Kitties, the total Kitty count is correct, and any other storage variables are correctly represented
Congratulations!
You've successfully created the backend of a fully functional Substrate chain capable of creating and managing Substrate Kitties. It could also be abstracted to other NFT-like use cases. Most importantly, at this point in the tutorial you should have all the knowledge you need to start creating your own pallet logic and dispatchable functions.
#
Next stepsComplete Part II of this tutorial to:
- Connect your chain to the front-end template
- Customize the template using PolkadotJS API
- Interact with kitty avatars using a custom front-end React app